﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ShareDeployed.Common.Extensions
{
	public static class TaskAsyncHelper
	{
		private abstract class VoidResult { }

		private static readonly Task _emptyTask = MakeEmpty();

		public static Task<TResult> ToTask<TResult>(this IEnumerable<Task> tasks)
		{
			var taskScheduler = SynchronizationContext.Current == null
					? TaskScheduler.Default : TaskScheduler.FromCurrentSynchronizationContext();
			var taskEnumerator = tasks.GetEnumerator();
			var completionSource = new TaskCompletionSource<TResult>();

			// Clean up the enumerator when the task completes.
			completionSource.Task.ContinueWith(t => taskEnumerator.Dispose(), taskScheduler);

			ToTaskDoOneStep(taskEnumerator, taskScheduler, completionSource, null);
			return completionSource.Task;
		}

		private static void ToTaskDoOneStep<TResult>(IEnumerator<Task> taskEnumerator, TaskScheduler taskScheduler,
													 TaskCompletionSource<TResult> completionSource, Task completedTask)
		{
			// Check status of previous nested task (if any), and stop if Canceled or Faulted.
			TaskStatus status;
			if (completedTask == null)
			{
				// This is the first task from the iterator; skip status check.
			}
			else if ((status = completedTask.Status) == TaskStatus.Canceled)
			{
				completionSource.SetCanceled();
				return;
			}
			else if (status == TaskStatus.Faulted)
			{
				completionSource.SetException(completedTask.Exception);
				return;
			}

			// Find the next Task in the iterator; handle cancellation and other exceptions.
			Boolean haveMore;
			try
			{
				haveMore = taskEnumerator.MoveNext();

			}
			catch (OperationCanceledException cancExc)
			{
				completionSource.SetCanceled();
				return;
			}
			catch (Exception exc)
			{
				completionSource.SetException(exc);
				return;
			}

			if (!haveMore)
			{
				// No more tasks; set the result (if any) from the last completed task (if any).
				// We know it's not Canceled or Faulted because we checked at the start of this method.
				if (typeof(TResult) == typeof(VoidResult))
				{        // No result
					completionSource.SetResult(default(TResult));
				}
				else if (!(completedTask is Task<TResult>))
				{     // Wrong result
					completionSource.SetException(new InvalidOperationException(
						"Asynchronous iterator " + taskEnumerator +
							" requires a final result task of type " + typeof(Task<TResult>).FullName +
							(completedTask == null ? ", but none was provided." :
								"; the actual task type was " + completedTask.GetType().FullName)));

				}
				else
				{
					completionSource.SetResult(((Task<TResult>)completedTask).Result);
				}
			}
			else
			{
				// When the nested task completes, continue by performing this function again.
				taskEnumerator.Current.ContinueWith(nextTask => ToTaskDoOneStep(
																taskEnumerator, taskScheduler, completionSource, nextTask),
													taskScheduler);
			}
		}

		private static Task MakeEmpty()
		{
			return FromResult<object>(null);
		}

		public static Task Empty
		{
			get
			{
				return _emptyTask;
			}
		}

		public static TTask Catch<TTask>(this TTask task) where TTask : Task
		{
			if (task != null && task.Status != TaskStatus.RanToCompletion)
			{
				task.ContinueWith(innerTask =>
				{
					var ex = innerTask.Exception;
					// observe Exception
#if !WINDOWS_PHONE && !SILVERLIGHT
					Trace.TraceError("SignalR exception thrown by Task: {0}", ex);
#endif
				}, TaskContinuationOptions.OnlyOnFaulted);
			}
			return task;
		}

		public static void ContinueWithNotComplete(this Task task, TaskCompletionSource<object> tcs)
		{
			task.ContinueWith(t =>
			{
				if (t.IsFaulted)
				{
					tcs.SetException(t.Exception);
				}
				else if (t.IsCanceled)
				{
					tcs.SetCanceled();
				}
			},
			TaskContinuationOptions.NotOnRanToCompletion);
		}

		public static void ContinueWith(this Task task, TaskCompletionSource<object> tcs)
		{
			task.ContinueWith(t =>
			{
				if (t.IsFaulted)
				{
					tcs.SetException(t.Exception);
				}
				else if (t.IsCanceled)
				{
					tcs.SetCanceled();
				}
				else
				{
					tcs.SetResult(null);
				}
			});
		}

		/// <summary>
		/// Passes a task returning function into another task returning function so that
		/// it can decide when it starts and returns a task that completes when all are finished
		/// </summary>
		public static Task Interleave<T>(Func<T, Action, Task> before, Func<Task> after, T arg)
		{
			var tcs = new TaskCompletionSource<object>();
			var tasks = new[] {
                            tcs.Task,
                            before(arg, () => after().ContinueWith(tcs))
                        };

			return tasks.Return();
		}

		public static Task Return(this Task[] tasks)
		{
			return Then(tasks, () => { });
		}

		// Then extesions
		public static Task Then(this Task task, Action successor)
		{
			switch (task.Status)
			{
				case TaskStatus.Faulted:
					return FromError(task.Exception);

				case TaskStatus.Canceled:
					return Canceled();

				case TaskStatus.RanToCompletion:
					return FromMethod(successor);

				default:
					return RunTask(task, successor);
			}
		}

		public static Task<TResult> Then<TResult>(this Task task, Func<TResult> successor)
		{
			switch (task.Status)
			{
				case TaskStatus.Faulted:
					return FromError<TResult>(task.Exception);

				case TaskStatus.Canceled:
					return Canceled<TResult>();

				case TaskStatus.RanToCompletion:
					return FromMethod(successor);

				default:
					return TaskRunners<object, TResult>.RunTask(task, successor);
			}
		}

		public static Task Then(this Task[] tasks, Action successor)
		{
			if (tasks.Length == 0)
			{
				return FromMethod(successor);
			}

			var tcs = new TaskCompletionSource<object>();
			Task.Factory.ContinueWhenAll(tasks, completedTasks =>
			{
				var faulted = completedTasks.FirstOrDefault(t => t.IsFaulted);
				if (faulted != null)
				{
					tcs.SetException(faulted.Exception);
					return;
				}
				var cancelled = completedTasks.FirstOrDefault(t => t.IsCanceled);
				if (cancelled != null)
				{
					tcs.SetCanceled();
					return;
				}

				successor();
				tcs.SetResult(null);
			});

			return tcs.Task;
		}

		public static Task Then<T1>(this Task task, Action<T1> successor, T1 arg1)
		{
			switch (task.Status)
			{
				case TaskStatus.Faulted:
					return FromError(task.Exception);

				case TaskStatus.Canceled:
					return Canceled();

				case TaskStatus.RanToCompletion:
					return FromMethod(successor, arg1);

				default:
					return GenericDelegates<object, object, T1, object>.ThenWithArgs(task, successor, arg1);
			}
		}

		public static Task Then<T1, T2>(this Task task, Action<T1, T2> successor, T1 arg1, T2 arg2)
		{
			switch (task.Status)
			{
				case TaskStatus.Faulted:
					return FromError(task.Exception);

				case TaskStatus.Canceled:
					return Canceled();

				case TaskStatus.RanToCompletion:
					return FromMethod(successor, arg1, arg2);

				default:
					return GenericDelegates<object, object, T1, T2>.ThenWithArgs(task, successor, arg1, arg2);
			}
		}

		public static Task Then<T1>(this Task task, Func<T1, Task> successor, T1 arg1)
		{
			switch (task.Status)
			{
				case TaskStatus.Faulted:
					return FromError(task.Exception);

				case TaskStatus.Canceled:
					return Canceled();

				case TaskStatus.RanToCompletion:
					return FromMethod(successor, arg1).FastUnwrap();

				default:
					return GenericDelegates<object, Task, T1, object>.ThenWithArgs(task, successor, arg1)
																	 .FastUnwrap();
			}
		}

		public static Task Then<T1, T2>(this Task task, Func<T1, T2, Task> successor, T1 arg1, T2 arg2)
		{
			switch (task.Status)
			{
				case TaskStatus.Faulted:
					return FromError(task.Exception);

				case TaskStatus.Canceled:
					return Canceled();

				case TaskStatus.RanToCompletion:
					return FromMethod(successor, arg1, arg2).FastUnwrap();

				default:
					return GenericDelegates<object, Task, T1, T2>.ThenWithArgs(task, successor, arg1, arg2)
																 .FastUnwrap();
			}
		}

		public static Task<TResult> Then<T, TResult>(this Task<T> task, Func<T, Task<TResult>> successor)
		{
			switch (task.Status)
			{
				case TaskStatus.Faulted:
					return FromError<TResult>(task.Exception);

				case TaskStatus.Canceled:
					return Canceled<TResult>();

				case TaskStatus.RanToCompletion:
					return FromMethod(successor, task.Result).FastUnwrap();

				default:
					return TaskRunners<T, Task<TResult>>.RunTask(task, t => successor(t.Result))
														.FastUnwrap();
			}
		}

		public static Task<TResult> Then<T, TResult>(this Task<T> task, Func<T, TResult> successor)
		{
			switch (task.Status)
			{
				case TaskStatus.Faulted:
					return FromError<TResult>(task.Exception);

				case TaskStatus.Canceled:
					return Canceled<TResult>();

				case TaskStatus.RanToCompletion:
					return FromMethod(successor, task.Result);

				default:
					return TaskRunners<T, TResult>.RunTask(task, t => successor(t.Result));
			}
		}

		public static Task<TResult> Then<T, T1, TResult>(this Task<T> task, Func<T, T1, TResult> successor, T1 arg1)
		{
			switch (task.Status)
			{
				case TaskStatus.Faulted:
					return FromError<TResult>(task.Exception);

				case TaskStatus.Canceled:
					return Canceled<TResult>();

				case TaskStatus.RanToCompletion:
					return FromMethod(successor, task.Result, arg1);

				default:
					return GenericDelegates<T, TResult, T1, object>.ThenWithArgs(task, successor, arg1);
			}
		}

		public static Task Then(this Task task, Func<Task> successor)
		{
			switch (task.Status)
			{
				case TaskStatus.Faulted:
					return FromError(task.Exception);

				case TaskStatus.Canceled:
					return Canceled();

				case TaskStatus.RanToCompletion:
					return FromMethod(successor).FastUnwrap();

				default:
					return TaskRunners<object, Task>.RunTask(task, successor)
													.FastUnwrap();
			}
		}

		public static Task<TResult> Then<TResult>(this Task task, Func<Task<TResult>> successor)
		{
			switch (task.Status)
			{
				case TaskStatus.Faulted:
					return FromError<TResult>(task.Exception);

				case TaskStatus.Canceled:
					return Canceled<TResult>();

				case TaskStatus.RanToCompletion:
					return FromMethod(successor).FastUnwrap();

				default:
					return TaskRunners<object, Task<TResult>>.RunTask(task, successor)
															 .FastUnwrap();
			}
		}

		public static Task Then<TResult>(this Task<TResult> task, Action<TResult> successor)
		{
			switch (task.Status)
			{
				case TaskStatus.Faulted:
					return FromError(task.Exception);

				case TaskStatus.Canceled:
					return Canceled();

				case TaskStatus.RanToCompletion:
					return FromMethod(successor, task.Result);

				default:
					return TaskRunners<TResult, object>.RunTask(task, successor);
			}
		}

		public static Task Then<TResult>(this Task<TResult> task, Func<TResult, Task> successor)
		{
			switch (task.Status)
			{
				case TaskStatus.Faulted:
					return FromError(task.Exception);

				case TaskStatus.Canceled:
					return Canceled();

				case TaskStatus.RanToCompletion:
					return FromMethod(successor, task.Result).FastUnwrap();

				default:
					return TaskRunners<TResult, Task>.RunTask(task, t => successor(t.Result))
													 .FastUnwrap();
			}
		}

		public static Task<TResult> Then<TResult, T1>(this Task<TResult> task, Func<Task<TResult>, T1, Task<TResult>> successor, T1 arg1)
		{
			switch (task.Status)
			{
				case TaskStatus.Faulted:
					return FromError<TResult>(task.Exception);

				case TaskStatus.Canceled:
					return Canceled<TResult>();

				case TaskStatus.RanToCompletion:
					return FromMethod(successor, task, arg1).FastUnwrap();

				default:
					return GenericDelegates<TResult, Task<TResult>, T1, object>.ThenWithArgs(task, successor, arg1)
																			   .FastUnwrap();
			}
		}

		public static Task FastUnwrap(this Task<Task> task)
		{
			var innerTask = (task.Status == TaskStatus.RanToCompletion) ? task.Result : null;
			return innerTask ?? task.Unwrap();
		}

		public static Task<T> FastUnwrap<T>(this Task<Task<T>> task)
		{
			var innerTask = (task.Status == TaskStatus.RanToCompletion) ? task.Result : null;
			return innerTask ?? task.Unwrap();
		}

		public static Task Delay(TimeSpan timeOut)
		{
			var tcs = new TaskCompletionSource<object>();

			var timer = new Timer(tcs.SetResult,
			null,
			timeOut,
			TimeSpan.FromMilliseconds(-1));

			return tcs.Task.ContinueWith(_ =>
			{
				timer.Dispose();
			},
			TaskContinuationOptions.ExecuteSynchronously);
		}

		public static Task AllSucceeded(this Task[] tasks, Action continuation)
		{
			return AllSucceeded(tasks, _ => continuation());
		}

		public static Task AllSucceeded(this Task[] tasks, Action<Task[]> continuation)
		{
			return Task.Factory.ContinueWhenAll(tasks, _ =>
			{
				var cancelledTask = tasks.FirstOrDefault(task => task.IsCanceled);
				if (cancelledTask != null)
					throw new TaskCanceledException();

				var allExceptions =
					tasks.Where(task => task.IsFaulted).SelectMany(task => task.Exception.InnerExceptions).ToList();

				if (allExceptions.Count > 0)
				{
					throw new AggregateException(allExceptions);
				}

				continuation(tasks);
			});
		}

		public static Task FromMethod(Action func)
		{
			try
			{
				func();
				return Empty;
			}
			catch (Exception ex)
			{
				return FromError(ex);
			}
		}

		public static Task FromMethod<T1>(Action<T1> func, T1 arg)
		{
			try
			{
				func(arg);
				return Empty;
			}
			catch (Exception ex)
			{
				return FromError(ex);
			}
		}

		public static Task FromMethod<T1, T2>(Action<T1, T2> func, T1 arg1, T2 arg2)
		{
			try
			{
				func(arg1, arg2);
				return Empty;
			}
			catch (Exception ex)
			{
				return FromError(ex);
			}
		}

		public static Task<TResult> FromMethod<TResult>(Func<TResult> func)
		{
			try
			{
				return FromResult<TResult>(func());
			}
			catch (Exception ex)
			{
				return FromError<TResult>(ex);
			}
		}

		public static Task<TResult> FromMethod<T1, TResult>(Func<T1, TResult> func, T1 arg)
		{
			try
			{
				return FromResult<TResult>(func(arg));
			}
			catch (Exception ex)
			{
				return FromError<TResult>(ex);
			}
		}

		public static Task<TResult> FromMethod<T1, T2, TResult>(Func<T1, T2, TResult> func, T1 arg1, T2 arg2)
		{
			try
			{
				return FromResult<TResult>(func(arg1, arg2));
			}
			catch (Exception ex)
			{
				return FromError<TResult>(ex);
			}
		}

		public static Task<T> FromResult<T>(T value)
		{
			var tcs = new TaskCompletionSource<T>();
			tcs.SetResult(value);
			return tcs.Task;
		}

		public static TaskContinueWithMethod GetContinueWith(Type taskType)
		{
			var continueWith = (from m in taskType.GetMethods()
								let methodParameters = m.GetParameters()
								where m.Name.Equals("ContinueWith", StringComparison.OrdinalIgnoreCase) &&
									methodParameters.Length == 1
								let parameter = methodParameters[0]
								where parameter.ParameterType.IsGenericType &&
								typeof(Func<,>) == parameter.ParameterType.GetGenericTypeDefinition()
								select new TaskContinueWithMethod
								{
									Method = m.MakeGenericMethod(typeof(Task)),
									Type = parameter.ParameterType.GetGenericArguments()[0]
								})
				.FirstOrDefault();
			return continueWith;
		}

		internal static Task FromError(Exception e)
		{
			var tcs = new TaskCompletionSource<object>();
			tcs.SetException(e);
			return tcs.Task;
		}

		internal static Task<T> FromError<T>(Exception e)
		{
			var tcs = new TaskCompletionSource<T>();
			tcs.SetException(e);
			return tcs.Task;
		}

		private static Task Canceled()
		{
			var tcs = new TaskCompletionSource<object>();
			tcs.SetCanceled();
			return tcs.Task;
		}

		private static Task<T> Canceled<T>()
		{
			var tcs = new TaskCompletionSource<T>();
			tcs.SetCanceled();
			return tcs.Task;
		}

		public class TaskContinueWithMethod
		{
			public MethodInfo Method { get; set; }
			public Type Type { get; set; }
		}

		private static Task RunTask(Task task, Action successor)
		{
			var tcs = new TaskCompletionSource<object>();
			task.ContinueWith(t =>
			{
				if (t.IsFaulted)
				{
					tcs.SetException(t.Exception);
				}
				else if (t.IsCanceled)
				{
					tcs.SetCanceled();
				}
				else
				{
					try
					{
						successor();
						tcs.SetResult(null);
					}
					catch (Exception ex)
					{
						tcs.SetException(ex);
					}
				}
			});

			return tcs.Task;
		}

		private static class TaskRunners<T, TResult>
		{
			internal static Task RunTask(Task<T> task, Action<T> successor)
			{
				var tcs = new TaskCompletionSource<object>();
				task.ContinueWith(t =>
				{
					if (t.IsFaulted)
					{
						tcs.SetException(t.Exception);
					}
					else if (t.IsCanceled)
					{
						tcs.SetCanceled();
					}
					else
					{
						try
						{
							successor(t.Result);
							tcs.SetResult(null);
						}
						catch (Exception ex)
						{
							tcs.SetException(ex);
						}
					}
				});

				return tcs.Task;
			}

			internal static Task<TResult> RunTask(Task task, Func<TResult> successor)
			{
				var tcs = new TaskCompletionSource<TResult>();
				task.ContinueWith(t =>
				{
					if (t.IsFaulted)
					{
						tcs.SetException(t.Exception);
					}
					else if (t.IsCanceled)
					{
						tcs.SetCanceled();
					}
					else
					{
						try
						{
							tcs.SetResult(successor());
						}
						catch (Exception ex)
						{
							tcs.SetException(ex);
						}
					}
				});

				return tcs.Task;
			}

			internal static Task<TResult> RunTask(Task<T> task, Func<Task<T>, TResult> successor)
			{
				var tcs = new TaskCompletionSource<TResult>();
				task.ContinueWith(t =>
				{
					if (task.IsFaulted)
					{
						tcs.SetException(t.Exception);
					}
					else if (task.IsCanceled)
					{
						tcs.SetCanceled();
					}
					else
					{
						try
						{
							tcs.SetResult(successor(t));
						}
						catch (Exception ex)
						{
							tcs.SetException(ex);
						}
					}
				});

				return tcs.Task;
			}
		}

		private static class GenericDelegates<T, TResult, T1, T2>
		{
			internal static Task ThenWithArgs(Task task, Action<T1> successor, T1 arg1)
			{
				return RunTask(task, () => successor(arg1));
			}

			internal static Task ThenWithArgs(Task task, Action<T1, T2> successor, T1 arg1, T2 arg2)
			{
				return RunTask(task, () => successor(arg1, arg2));
			}

			internal static Task<TResult> ThenWithArgs(Task task, Func<T1, TResult> successor, T1 arg1)
			{
				return TaskRunners<object, TResult>.RunTask(task, () => successor(arg1));
			}

			internal static Task<TResult> ThenWithArgs(Task task, Func<T1, T2, TResult> successor, T1 arg1, T2 arg2)
			{
				return TaskRunners<object, TResult>.RunTask(task, () => successor(arg1, arg2));
			}

			internal static Task<TResult> ThenWithArgs(Task<T> task, Func<T, T1, TResult> successor, T1 arg1)
			{
				return TaskRunners<T, TResult>.RunTask(task, t => successor(t.Result, arg1));
			}

			internal static Task<Task> ThenWithArgs(Task task, Func<T1, Task> successor, T1 arg1)
			{
				return TaskRunners<object, Task>.RunTask(task, () => successor(arg1));
			}

			internal static Task<Task<TResult>> ThenWithArgs(Task<T> task, Func<T, T1, Task<TResult>> successor, T1 arg1)
			{
				return TaskRunners<T, Task<TResult>>.RunTask(task, t => successor(t.Result, arg1));
			}

			internal static Task<Task<T>> ThenWithArgs(Task<T> task, Func<Task<T>, T1, Task<T>> successor, T1 arg1)
			{
				return TaskRunners<T, Task<T>>.RunTask(task, t => successor(t, arg1));
			}
		}
	}
}
